﻿
#include "log.h"
#include <filesystem>
#include <cstdarg>
#include <memory>
#include <assert.h>
#include <fstream>
#include <filesystem>
#include <iostream>

#include "cross_comm.h"
#include "as_scopegrard.h"
#include "thread_name.h"
#include "time_value.h"

using namespace as::tools;
namespace fs = std::filesystem;

Log *Log::log_inc = nullptr;
std::function<void(const std::string_view &str)> Log::g_output[] = {
{
	[](const std::string_view &str) 
	{
		Log::get_ins()->async_info(as::tools::LOG_TYPE_INFO, str.data());
	}
},
{
	[](const std::string_view &str) 
	{
		Log::get_ins()->async_info(as::tools::LOG_TYPE_WARNING, str.data());
	}
},
{
	[](const std::string_view &str) 
	{
		Log::get_ins()->async_info(as::tools::LOG_TYPE_ERROR, str.data());
	}
},
{
	[](const std::string_view &str) 
	{
		Log::get_ins()->async_info(as::tools::LOG_TYPE_FATAL, str.data());
		std::unique_lock<Mutex> l(Log::get_ins()->m_);
		Log::get_ins()->HandleTask(true);
	}
}
};

LogBase::LogBase(const std::string_view &path, int line, uint8_t type)
	:type_(type)
{
	std::string fileName(path);
	fileName = fileName.substr(fileName.find_last_of("/\\") + 1);
	stream_ << "[" << fileName.c_str() << ":" << line << "]";
}

LogBase::~LogBase()
{
	stream_ << "\n";
	Log::g_output[type_](stream_.GetString().c_str());
}

as::tools::LogStream& LogBase::stream()
{
	return stream_;
}

std::string Log::tostring(int type)
{
	switch (type)
	{
	case LOG_TYPE_INFO:
	{
		return "INFO";
	}
	case LOG_TYPE_WARNING:
	{
		return "WARNING";
	}
	case LOG_TYPE_ERROR:
	{
		return "ERROR";
	}
	case LOG_TYPE_FATAL:
	{
		return "FATAL";
	}
	default:
		return "EMPTY";
	}
}

bool Log::WriteFile(uint8_t type, const std::string &str)
{
	static thread_local std::ofstream of;
	static thread_local std::string saveFile;
	saveFile.clear();
	saveFile += log_name_[type] + "_";
	saveFile += base_name_;
	saveFile += TimeValue::GetCurTimeStr("[%Y-%m-%d]");
	saveFile.append(".log");

	fs::path dir = fs::path(saveFile).parent_path();
	if (fs::exists(dir) == false)
	{
		fs::create_directories(dir);
	}

	of.open(saveFile, std::ios_base::app);
	if (of.is_open() == false)
	{
		log_queue_.push({ type, str });
		return false;
	}

	of.write(str.c_str(), str.size());
	of.close();
	if (console_output_type_ <= type)
	{
		printf("%s", str.c_str());
	}		
	return true;
}

Log::Log()
{
	std::string base = "./log/log_";
	log_name_[LOG_TYPE_INFO] = base + "info";
	log_name_[LOG_TYPE_WARNING] = base + "warning";
	log_name_[LOG_TYPE_FATAL] = base + "fatal";
	log_name_[LOG_TYPE_ERROR] = base + "error";
	work_thread_ = std::thread([this]() 
		{
			this->run(); 
		}
	);
}


Log::~Log()
{
	is_stop_ = true;
	expired_cond_.notify_all();
	work_thread_.join();
	HandleTask(true);
}

std::string Log::get_format(const char *_format, va_list &ap)
{
	static thread_local char str[1024] = { 0 };
	memset(str, 0, sizeof(str));
	int s = vsnprintf_s(str, sizeof(str), _format, ap);
	return str;
}

#define AS_LOG_PACKET(TYPE)			                \
{if(is_stop_) return 0;								\
va_list __my_ap;						            \
va_start(__my_ap, _format);		                    \
std::string __str = get_format(_format, __my_ap) + "\n";	\
async_info(TYPE, __str.c_str());                    \
return __str.length();}

size_t Log::log_format(const char *_format, ...)
{
	AS_LOG_PACKET(LOG_TYPE_INFO);
}

size_t Log::log_err_format(const char *_format, ...)
{
	AS_LOG_PACKET(LOG_TYPE_ERROR);
}

size_t Log::log_warn_format(const char *_format, ...)
{
	AS_LOG_PACKET(LOG_TYPE_WARNING);
}

size_t Log::log_fatal_format(const char *_format, ...)
{
	AS_LOG_PACKET(LOG_TYPE_FATAL);
}

void Log::run()
{
	while(is_stop_ == false)
	{
		std::unique_lock<Mutex> l(m_);
		expired_cond_.wait(l,
			[this]()
			{
				return is_stop_ || log_queue_.size() > 0;
			}
		);
		HandleTask();
	}
}

void Log::run_one()
{
	std::unique_lock<Mutex> l(m_);
	HandleTask();
}
int Log::async_info(uint8_t type, const char *str, ...)
{
	std::string curTimeStr = TimeValue::GetCurTimeStr();
	va_list va;
	va_start(va, str);

	static thread_local char str_cache[10240];
	memset(str_cache, 0, sizeof(str_cache));
	snprintf_s(str_cache, sizeof(str_cache), "[%s %s %u]", tostring(type).c_str(), curTimeStr.c_str(), as::thread::GetThreadId());

	int headSize = (int)strlen(str_cache);
	char *aftstr = &(str_cache[headSize]);
	int count = vsnprintf_s(aftstr, sizeof(str_cache) - headSize, str, va);
	if (count > 0)
	{
		if (type == LOG_TYPE_FATAL)
		{
			WriteFile(type, std::string(str_cache));
			assert(false);
		}

		std::unique_lock<Mutex> l(m_);
		log_queue_.push({ type, str_cache });
		expired_cond_.notify_one();
	}
	return 0;
}

void Log::HandleTask(bool isQuit)
{
	static thread_local std::map<uint8_t, std::string> total_str_map;
	total_str_map.clear();

	{
		if (log_queue_.empty())
			return;

		while (log_queue_.empty() == false)
		{
			auto &[type, str] = log_queue_.front();
			total_str_map[type] += str;
			log_queue_.pop();
			if (total_str_map[type].size() > 30000)
			{
				if (isQuit == false)
					break;
			}
		};
	}

	for (auto &it : total_str_map)
	{
		if(WriteFile(it.first, it.second) == false)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			break;
		}
	}
}

void Log::stop()
{
	is_stop_ = true;
	expired_cond_.notify_all();
}


